// pasmo.cpp
// Revision 14-jan-2007
#pragma warning(disable : 4996)

#include "pasmo.h"
#include "asm.h"
#include "errorsbuf.h"

//#include <stdint.h>
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <stdexcept>

using std::cout;
using std::cerr;
using std::endl;

#ifdef _WIN32
#include <direct.h>
//#include <intrin.h>
#endif

namespace {

using std::string;
using std::vector;
using std::runtime_error;

class Usage { };

class NeedArgument : public runtime_error {
public:
    NeedArgument (const string & option) :
        runtime_error ("Option " + option + " requires argument")
    { }
};

class InvalidOption : public runtime_error {
public:
    InvalidOption (const string & option) :
        runtime_error ("Invalid option: " + option)
    { }
};

runtime_error CreateObjectError ("Error creating object file");
runtime_error SymbolFileError ("Error creating symbols file");
runtime_error PublicFileError ("Error creating public symbols file");


std::ostream * perr= & cerr;


const string opt1 ("-1");
const string opt8 ("-8");
const string optd ("-d");
const string optv ("-v");
const string optB ("-B");
const string optE ("-E");
const string optI ("-I");

const string opt86        ("--86");
const string optalocal    ("--alocal");
const string optamsdos    ("--amsdos");
const string optbin       ("--bin");
const string optbracket   ("--bracket");
const string optcdt       ("--cdt");
const string optcdtbas    ("--cdtbas");
const string optcmd       ("--cmd");
const string optequ       ("--equ");
const string opterr       ("--err");
const string opthex       ("--hex");
const string optmsx       ("--msx");
const string optname      ("--name");
const string optnocase    ("--nocase");
const string optplus3dos  ("--plus3dos");
const string optprl       ("--prl");
const string optpublic    ("--public");
const string opttap       ("--tap");
const string opttapbas    ("--tapbas");
const string opttzx       ("--tzx");
const string opttzxbas    ("--tzxbas");
const string optw8080     ("--w8080");


class Options {
public:
    //Options (int argc, char * * argv);
    Options(COMPILE_IN* i_CompileArgs, COMPILED_INFO* o_CompiledInfo);

    typedef void (Asm::* emitfunc_t) (std::ostream &);

    emitfunc_t getemit () const { return emitfunc; }
    bool redirerr () const { return redirecterr; }
    bool publiconly () const { return emitpublic; }
   void   setFileIn (string inFileName) { m_inFile = inFileName; }
    string getfilein () const { return m_inFile; }
    string getfileout () const { return m_outFile; }
    string getfilesymbol () const { return filesymbol; }
    string getfilepublic () const;
    string getheadername () const { return headername; }
    void apply (Asm & assembler) const;
private:
    emitfunc_t emitfunc;
    static const emitfunc_t emitdefault;

    bool verbose;
    bool emitpublic;
    Asm::DebugType debugtype;
    bool redirecterr;
    bool nocase;
    bool autolocal;
    bool bracketonly;
    bool warn8080;
    bool mode86;

    vector <string> includedir;
    vector <string> labelpredef;

   //Data
   string m_inAsm; //assembler code in(to be compiled)

   //Files
    string m_inFile;
    string m_outFile;
    string filesymbol;
    string filepublic;
    string headername;
};


const Options::emitfunc_t Options::emitdefault (& Asm::emitobject);
static void CreateDummyFileForCompilation(const char* i_chrSourceCode, std::string& o_dummyFilePath);

static void SetCurrentDir(const char* i_fileNamePath)
{
    std::string fullFilename = i_fileNamePath;

    std::string outStrPath;
    size_t i = fullFilename.rfind(PATH_SEPARATOR, fullFilename.length());
    if (i != string::npos) {
        outStrPath = fullFilename.substr(0, i);
    }
    outStrPath += PATH_SEPARATOR;
    _chdir(outStrPath.c_str());
}

Options::Options (COMPILE_IN* i_CompileArgs, COMPILED_INFO* o_CompiledInfo) :
    emitfunc (emitdefault),
    verbose (false),
    emitpublic (false),
    debugtype (Asm::NoDebug),
    redirecterr (false),
    nocase (false),
    autolocal (false),
    bracketonly (false),
    warn8080 (false),
    mode86 (false)
{
    //int argpos;

    m_inFile.clear();
    m_outFile.clear();

    /*for (argpos = 0; argpos < argc; ++argpos)
    {
        const string arg (argv [argpos] );
        if (arg == optbin || arg == "--binfile")
            emitfunc= & Asm::emitobject;
        else if (arg == opthex)
            emitfunc= & Asm::emithex;
        else if (arg == optprl)
            emitfunc= & Asm::emitprl;
        else if (arg == optcmd)
            emitfunc= & Asm::emitcmd;
        else if (arg == optplus3dos)
            emitfunc= & Asm::emitplus3dos;
        else if (arg == opttap)
            emitfunc= & Asm::emittap;
        else if (arg == opttzx)
            emitfunc= & Asm::emittzx;
        else if (arg == optcdt)
            emitfunc= & Asm::emitcdt;
        else if (arg == opttapbas)
            emitfunc= & Asm::emittapbas;
        else if (arg == opttzxbas)
            emitfunc= & Asm::emittzxbas;
        else if (arg == optcdtbas)
            emitfunc= & Asm::emitcdtbas;
        else if (arg == optamsdos)
            emitfunc= & Asm::emitamsdos;
        else if (arg == optmsx)
            emitfunc= & Asm::emitmsx;
        else if (arg == optpublic)
            emitpublic= true;
        else if (arg == optname)
        {
            ++argpos;
            if (argpos >= argc)
                throw NeedArgument (optname);
            headername= argv [argpos];
        }
        else if (arg == optv)
            verbose= true;
        else if (arg == optd)
            debugtype= Asm::DebugSecondPass;
        else if (arg == opt1)
            debugtype= Asm::DebugAll;
        else if (arg == opterr)
            redirecterr= true;
        else if (arg == optnocase)
            nocase= true;
        else if (arg == optalocal)
            autolocal= true;
        else if (arg == optB)
            bracketonly= true;
        else if (arg == optbracket)
            bracketonly= true;
        else if (arg == opt8 || arg == optw8080)
            warn8080= true;
        else if (arg == opt86)
            mode86= true;
        else if (arg == optI)
        {
            ++argpos;
            if (argpos >= argc)
                throw NeedArgument (optI);
            //a.addincludedir (argv [argpos] );
            includedir.push_back (argv [argpos] );
        }
        else if (arg == optE || arg == optequ)
        {
            ++argpos;
            if (argpos >= argc)
                throw NeedArgument (arg);
            labelpredef.push_back (argv [argpos] );
        }
        else if (arg == "--")
        {
            ++argpos;
            break;
        }
        else if (arg.substr (0, 1) == "-")
            throw InvalidOption (arg);
        else
            break;
    }
    */

    //Validation checks
    if (i_CompileArgs->CompileMode == BIN)
    {
        if (i_CompileArgs->chrSourceCode == NULL)
        {
            FILL_ERROR(o_CompiledInfo->czErrMessage, "For BIN mode source code is expected as an input...Error!\n\n");
            return;
        }
    }
    else if (i_CompileArgs->cInputIsFile == 'Y' && (i_CompileArgs->chrFileIn == NULL || i_CompileArgs->chrFileIn[0] == '\0'))
    {
        FILL_ERROR(o_CompiledInfo->czErrMessage, "Missing file name to compile although input declared as file type...Error!\n\n")
        return;
    }

    if (i_CompileArgs->CompileMode == BIN || (i_CompileArgs->CompileMode == TAP_BAS && i_CompileArgs->cInputIsFile != 'Y'))
    {
        std::string dummyFile;
        CreateDummyFileForCompilation(i_CompileArgs->chrSourceCode, dummyFile);
        m_inFile = dummyFile;
        i_CompileArgs->cInputIsFile = 'Y';
    }
    else
    {
        if (i_CompileArgs->chrFileIn == NULL)
        {
            FILL_ERROR(o_CompiledInfo->czErrMessage, "ASSERT: Input file is NULL...Error!\n\n")
            return;
        }

        //input file
        m_inFile = i_CompileArgs->chrFileIn;
    }

    //Compile mode
    switch (i_CompileArgs->CompileMode)
    {
        case BIN:
            emitfunc = &Asm::emitobject;
            break;
        case TAP_BAS:
        case BIN_FILE:
            //output file
            if (i_CompileArgs->CompileMode == TAP_BAS)
            {
                if (i_CompileArgs->chrFileOut[0] == '\0')
                {
                    FILL_ERROR(o_CompiledInfo->czErrMessage, "Missing output file for mode TAP_BAS...Error!\n\n")
                    return;
                }
                m_outFile = i_CompileArgs->chrFileOut;
            }

            //Set current dir
            SetCurrentDir(m_inFile.c_str());

            emitfunc = i_CompileArgs->CompileMode == TAP_BAS ? &Asm::emittapbas : emitfunc = &Asm::emitobject;
            break;
        default:
            FILL_ERROR(o_CompiledInfo->czErrMessage, "Incorrect compile parameter...Error!\n\n")
            return;
    }

    //++argpos;
    //++argpos;

    if (i_CompileArgs->cEmitSymbols == 'Y')
    {
        //filesymbol = i_CompileArgs->chrFileSymbols;
        //++argpos;

        /*if (! emitpublic && argpos < argc)
        {
            filepublic= argv [argpos];
            ++argpos;
        }

        if (argpos < argc)
            cerr << "WARNING: Extra arguments ignored" << endl;
        */
    }

    if (headername.empty () )
        headername= m_outFile;
}

string Options::getfilepublic () const
{
    if (emitpublic)
        return filesymbol;
    else
        return filepublic;
}

void Options::apply (Asm & assembler) const
{
    assembler.setdebugtype (debugtype);

    if (verbose)
        assembler.verbose ();
    if (redirecterr)
        assembler.errtostdout ();
    if (nocase)
        assembler.caseinsensitive ();
    if (autolocal)
        assembler.autolocal ();
    if (bracketonly)
        assembler.bracketonly ();
    if (warn8080)
        assembler.warn8080 ();
    if (mode86)
        assembler.set86 ();

    for (size_t i= 0; i < includedir.size (); ++i)
        assembler.addincludedir (includedir [i] );

    for (size_t i= 0; i < labelpredef.size (); ++i)
        assembler.addpredef (labelpredef [i] );

    assembler.setheadername (headername);
}

static void PrintHelp()
{
    printf("\nUsage examples:\n");
    printf("  pasmo2.exe --binfile <file or assembler code>\n");
    printf("  pasmo2.exe --tapbas <file or assembler code> <outputfile.tap>\n\n");
}

static void CreateDummyFileForCompilation(const char* i_chrSourceCode, std::string& o_dummyFilePath)
{
    std::string dummy;
    FILE* tempFile;

    //get temp file which will be compiled
    dummy = getenv("TEMP");
    dummy += PATH_SEPARATOR"dummy.asm";

    tempFile = fopen(dummy.c_str(), "w");
    if (tempFile == NULL)
        return; // 1;

    fputs(i_chrSourceCode, tempFile);
    fclose(tempFile);

    o_dummyFilePath = dummy;
}

int doit (COMPILE_IN* in_Data, COMPILED_INFO* out_Compiled)
{
    int retCode = 0;

    CompilationErrors::Clear();

    //if input parameter is source code then here the source code will be put into a file because the Pasmo "architecture" is designed to work with files only
    /*if (in_Data->cInputIsFile == 'Y' && in_Data->CompileMode != BIN/*BIN mode has input source code always/)
    {
        std::ifstream ifs(in_Data->chrFileIn);
        std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
        //COPY_CZ(in_Data->chrSourceCode, content.c_str());
    }*/

    // Process command line options.
    Options option(in_Data, out_Compiled);
    if (out_Compiled->czErrMessage != NULL) //error ?
        return 1;

    if (option.redirerr () )
        perr= & cout;

    // Assemble.
    Asm assembler;

    option.apply (assembler);

   if( option.getfilein().empty() )
   {
      //option.setFileIn(dummy);
      assembler.loadfile ( option.getfilein () );
   }
   else
       assembler.loadfile ( option.getfilein () );

    //Compile core
    assembler.processfile ();

    // Generate output file.
    std::ofstream out (option.getfileout ().c_str (),
        std::ios::out | std::ios::binary);
    if (! out.is_open () )
    {
        //no output file => put the disassembly on stdout
        assembler.emitbytes(out_Compiled);
    }
    else
    {
        (assembler.* option.getemit () ) (out);
        out.close ();
    }

    // Generate symbol table and public symbol table if required.

    string filesymbol= option.getfilesymbol ();
    if (in_Data->cEmitSymbols == 'Y') //dump symbols ?
    {
        /*std::ofstream sout;
        std::streambuf * cout_buf= 0;
        if (filesymbol != "-")
        {
            sout.open (filesymbol.c_str () );
            if (! sout.is_open () )
                throw SymbolFileError;
            cout_buf= cout.rdbuf ();
            cout.rdbuf (sout.rdbuf () );
        }
        assembler.dumpsymbol (cout);
        if (cout_buf)
        {
            cout.rdbuf (cout_buf);
            sout.close ();
        }*/
        assembler.dumpsymbol(out_Compiled);
    }

    string filepublic= option.getfilepublic ();
    if (! filepublic.empty () )
    {
        std::ofstream sout;
        std::streambuf * cout_buf= 0;
        if (filepublic != "-")
        {
            sout.open (filepublic.c_str () );
            if (! sout.is_open () )
                throw PublicFileError;
            cout_buf= cout.rdbuf ();
            cout.rdbuf (sout.rdbuf () );
        }
        assembler.dumppublic (cout);
        if (cout_buf)
        {
            cout.rdbuf (cout_buf);
            sout.close ();
        }
    }

    return 0;
}


} // namespace

extern "C" __declspec(dllexport) int compile( COMPILE_IN* in_Data,
                                              COMPILED_INFO* out_Compiled
)
{
    int  retCode = 1; // 0  = OK
    char **args;
    args = new char *[3];
    args[0] = new char[20];

    try
    {
        /*FILE* file = fopen("D:\\log.log", "w");

        char czTemp[1024];
        sprintf(czTemp, "Pointer CompileMode: %p   Value: %d\n", &in_Data->CompileMode, in_Data->CompileMode);
        fputs(czTemp, file);
        sprintf( czTemp, "Pointer chrSourceCode: %p\n",  in_Data->chrSourceCode);
        fputs(czTemp, file);
        sprintf( czTemp, "Pointer iCompiledSize: %p   Value: %d\n",  &out_Compiled->iCompiledSize, out_Compiled->iCompiledSize);
        fputs(czTemp, file);
        sprintf( czTemp, "Pointer czCompiled: %p   Value: %d\n",  &out_Compiled->czCompiled, out_Compiled->czCompiled);
        fputs(czTemp, file);
        sprintf( czTemp, "Pointer errFileLine: %p\n",  errFileLine);
        fputs(czTemp, file);
        sprintf( czTemp, "Pointer errFileName: %p\n",  errFileName);
        fputs(czTemp, file);
        sprintf( czTemp, "Pointer errReason: %p\n",  errReason);
        fputs(czTemp, file);
        fclose(file);*/

        //dll version
        if (in_Data->CompileMode == _version)
        {
            char* VERSION = (char*)"v1.03";
            out_Compiled->czCompiled = (char*)malloc(strlen(VERSION));
            strcpy(out_Compiled->czCompiled, VERSION);
            return 0;
        }
        //dll architecture(x86 or x64)
        if (in_Data->CompileMode == _machine)
        {
            /*int CPUInfo[4];
            __cpuid(CPUInfo, 0);
            return (CPUInfo[3] & 0x20000000) || false;*/
            int intSize = sizeof(int);

            out_Compiled->czCompiled = (char*)malloc(3);
            strcpy(out_Compiled->czCompiled, intSize == 4 ? "x86" : "x64");
            return 0;
        }

        //clear output structure
        out_Compiled->iErrFileLine = -1;
        if (out_Compiled->czErrMessage != NULL)
            out_Compiled->czErrMessage = NULL;

        retCode = doit(in_Data, out_Compiled);
    }
    catch (std::logic_error & e)
    {
        sprintf(out_Compiled->czErrMessage, "Unexpected error: %s\n", (char*)e.what());
    }
    catch (std::exception & e)
    {
        out_Compiled->czErrMessage = new char[1024];
        if (CompilationErrors::GetErrorFullDesc()[0] != 0)
            sprintf(out_Compiled->czErrMessage,
                "%s in \"%s\"\n",
                (char*)e.what(),
                CompilationErrors::GetErrorFullDesc().c_str()
                );
        else
            sprintf(out_Compiled->czErrMessage,
            "%s\n",
            (char*)e.what()
            );
        //printf(errReason);
        out_Compiled->iErrFileLine = CompilationErrors::GetErrorFileLine();
        out_Compiled->czErrFileName = new char[strlen(CompilationErrors::GetErrorFileName().c_str())+1];
        strncpy(out_Compiled->czErrFileName, CompilationErrors::GetErrorFileName().c_str(), strlen(CompilationErrors::GetErrorFileName().c_str()));
        out_Compiled->czErrFileName[strlen(CompilationErrors::GetErrorFileName().c_str())] = '\0';
    }
    catch (Usage &)
    {
        /*cerr <<    "Pasmo v. <undefined> "
            " (C) 2004-2005 Julian Albo\n\n"
            "Usage:\n\n"
            "\tpasmo [options] source object [symbol]\n\n"
            "See the README file for details.\n";*/
    }
    catch (...)
    {
        sprintf(out_Compiled->czErrMessage, "ERROR: Unexpected exception. Please contact us.\n");
    }

    // Added to fix Debian bug report #394733
    return retCode;
}

int main (int argc, char * * argv)
{
    COMPILE_IN* compileIn = new COMPILE_IN();
    COMPILED_INFO* compiledOut = new COMPILED_INFO();

    int  retCode = 1; // 0  = OK
    bool fBinFileMode, fTapBasMode;

    //print dll version
    compileIn->CompileMode = _version;
    retCode = compile(compileIn, compiledOut);
    if (retCode != 0)
    {
        printf("Error getting dll version!");
        return 1;
    }
    printf("Version is: %s\n", compiledOut->czCompiled);

    if (argc < 2)
    {
        printf("Missing arguments...error!\n");
        PrintHelp();
        return retCode;
    }

    fBinFileMode = (strcmp(argv[1], "--binfile") == 0);
    fTapBasMode = (strcmp(argv[1], "--tapbas") == 0);
    if ((fBinFileMode || fTapBasMode) && argc < 3)
    {
        printf("Missing input file name...Error!\n");
        PrintHelp();
        return retCode;
    }
    if (fTapBasMode && argc < 4)
    {
        printf("Missing output file name...Error!\n");
        PrintHelp();
        return retCode;
    }

    // Set compile parameters
    //--------------------------------------------------------------------------------------------------------------------------------
    // use of argv[1]: a.) "--bin"     => input is source code, output is memory
    //                 b.) "--binfile" => input is file, output is memory
    //                 c.) "--tapbas"  => generate .tap file with BASIC header; save the file to path in argv[3]
    //                 d.) "--version" => shows dll version(out will be put into COMPILED_INFO->czCompiled)
    //                 e.) "--machine" => returns "x86" or "x64" into COMPILED_INFO->czCompiled depending on machine architecture type
    if (fBinFileMode)
    {
        compileIn->chrFileIn = argv[2];
        compileIn->cInputIsFile = 'Y';
        compileIn->CompileMode = BIN_FILE;
    }
    else if (fTapBasMode) // TAP_BAS
    {
        //compileIn->chrFileIn = argv[2];
        compileIn->chrSourceCode = (char*)"org 40000\nxor a\nout(#fe),a\nret";
        //compileIn->cInputIsFile = 'Y';
        compileIn->CompileMode = TAP_BAS;
        compileIn->chrFileOut = argv[3];
    }
    else if (!strcmp(argv[1], "--bin"))
    {
        compileIn->CompileMode = BIN;
        //compileIn->chrSourceCode = (char*)"org 40000\nxor a\nout(#fe),a\nret";
		//compileIn->chrSourceCode = (char*)"org 40000";
		compileIn->chrSourceCode = (char*)"org 40000\nret";
    }
    else
    {
        printf("Incorrect compile parameter %s...Error!\n", argv[1]);
        PrintHelp();
        return retCode;
    }

    //emit symbols
    compileIn->cEmitSymbols = 'Y';

    try
    {
        printf("Compilation started...\n\n");

        retCode = compile(compileIn, compiledOut);
        if (compileIn->CompileMode == BIN)
        {
           if (retCode == 0)
           {
               if ( compileIn->cEmitSymbols == 'Y' && compiledOut->arrSourceSymbols != NULL)
               {
                   FILE* suborSymbols = fopen("compiledSymbols.asm", "w+");
                   fwrite(compiledOut->arrSourceSymbols, 1, strlen(compiledOut->arrSourceSymbols), suborSymbols);
                   fclose(suborSymbols);
               }
           }
           else
           {
               printf("ERROR: %s", compiledOut->czErrMessage);
               //if (fBinFileMode)
               {
                   printf("   file:      %s\n", compiledOut->czErrFileName);
                   printf("   file line: %d\n\n", compiledOut->iErrFileLine);
               }
           }
       }
       else if (fTapBasMode)
       {
           if (retCode != 0)
           {
               printf("ERROR: %s", compiledOut->czErrMessage);
               //if (fBinFileMode)
               {
                   printf("   file:      %s\n", compiledOut->czErrFileName);
                   printf("   file line: %d\n\n", compiledOut->iErrFileLine);
               }
           }
       }

        printf("Compilation finished...\n");
    }
    catch (std::logic_error & e)
    {
        * perr << "ERROR: " << e.what () << endl <<
            "This error is unexpected, please "
            "send a bug report." << endl;
    }
    catch (std::exception & e)
    {
        * perr << "ERROR: " << e.what () << endl;
    }
    catch (Usage &)
    {
        cerr <<    "Pasmo v. <undefined> "
            " (C) 2004-2005 Julian Albo\n\n"
            "Usage:\n\n"
            "\tpasmo [options] source object [symbol]\n\n"
            "See the README file for details.\n";
    }
    catch (...)
    {
        cerr << "ERROR: Unexpected exception.\n"
            "Please send a bug report.\n";
    }

    // Added to fix Debian bug report #394733
    return retCode;
}

// End of pasmo.cpp
